package com.pivotal.cloud.crypto.argon2;

import org.bouncycastle.crypto.params.Argon2Parameters;

import java.util.Arrays;
import java.util.Base64;

/**
 * @className: com.pivotal.cloud.security.crypto.argon2.Argon2
 * @projectName: PivotalCloud项目
 * @module: PivotalCloud项目-Argon2类，主要位于Security安全模块-Argon2工具类模块
 * @content: Argon2-Argon2工具类
 * @author: Powered by Marklin
 * @datetime: 2025-06-25 21:32
 * @version: 1.0.0
 * @copyright: Copyright © 2018-2025 PivotalCloud Systems Incorporated. All rights
 * reserved.
 */
public class Argon2 {

	private static final Base64.Encoder b64encoder = Base64.getEncoder().withoutPadding();

	private static final Base64.Decoder b64decoder = Base64.getDecoder();

	private Argon2() {
	}

	public static String encode(byte[] hash, Argon2Parameters parameters) throws IllegalArgumentException {
		StringBuilder stringBuilder = new StringBuilder();
		String type;
		switch (parameters.getType()) {
		case Argon2Parameters.ARGON2_d:
			type = "$argon2d";
			break;
		case Argon2Parameters.ARGON2_i:
			type = "$argon2i";
			break;
		case Argon2Parameters.ARGON2_id:
			type = "$argon2id";
			break;
		default:
			throw new IllegalArgumentException("Invalid algorithm type: " + parameters.getType());
		}
		stringBuilder.append(type);
		stringBuilder.append("$v=").append(parameters.getVersion()).append("$m=").append(parameters.getMemory())
				.append(",t=").append(parameters.getIterations()).append(",p=").append(parameters.getLanes());
		if (parameters.getSalt() != null) {
			stringBuilder.append("$").append(b64encoder.encodeToString(parameters.getSalt()));
		}
		stringBuilder.append("$").append(b64encoder.encodeToString(hash));
		return stringBuilder.toString();
	}

	public static Argon2Hash decode(String encodedHash) throws IllegalArgumentException {
		Argon2Parameters.Builder paramsBuilder;
		String[] parts = encodedHash.split("\\$");
		if (parts.length < 4) {
			throw new IllegalArgumentException("Invalid encoded Argon2-hash");
		}
		int currentPart = 1;
		switch (parts[currentPart++]) {
		case "argon2d":
			paramsBuilder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_d);
			break;
		case "argon2i":
			paramsBuilder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_i);
			break;
		case "argon2id":
			paramsBuilder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id);
			break;
		default:
			throw new IllegalArgumentException("Invalid algorithm type: " + parts[1]);
		}
		if (parts[currentPart].startsWith("v=")) {
			paramsBuilder.withVersion(Integer.parseInt(parts[currentPart].substring(2)));
			currentPart++;
		}
		String[] performanceParams = parts[currentPart++].split(",");
		if (performanceParams.length != 3) {
			throw new IllegalArgumentException("Amount of performance parameters invalid");
		}
		if (!performanceParams[0].startsWith("m=")) {
			throw new IllegalArgumentException("Invalid memory parameter");
		}
		paramsBuilder.withMemoryAsKB(Integer.parseInt(performanceParams[0].substring(2)));
		if (!performanceParams[1].startsWith("t=")) {
			throw new IllegalArgumentException("Invalid iterations parameter");
		}
		paramsBuilder.withIterations(Integer.parseInt(performanceParams[1].substring(2)));
		if (!performanceParams[2].startsWith("p=")) {
			throw new IllegalArgumentException("Invalid parallelity parameter");
		}
		paramsBuilder.withParallelism(Integer.parseInt(performanceParams[2].substring(2)));
		paramsBuilder.withSalt(b64decoder.decode(parts[currentPart++]));
		return new Argon2Hash(b64decoder.decode(parts[currentPart]), paramsBuilder.build());
	}

	public static class Argon2Hash {

		private byte[] hash;

		private Argon2Parameters parameters;

		public Argon2Hash(byte[] hash, Argon2Parameters parameters) {
			this.hash = Arrays.copyOf(hash, hash.length);
			this.parameters = parameters;
		}

		public byte[] getHash() {
			return Arrays.copyOf(hash, hash.length);
		}

		public void setHash(byte[] hash) {
			this.hash = Arrays.copyOf(hash, hash.length);
		}

		public Argon2Parameters getParameters() {
			return this.parameters;
		}

		public void setParameters(Argon2Parameters parameters) {
			this.parameters = parameters;
		}

	}

}
